package common.bean;


import common.annotation.DaoSql;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.*;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DaoProxyBean implements MethodInterceptor, InitializingBean, FactoryBean<Object>, ApplicationContextAware {
    final static Log log = LogFactory.getLog(DaoProxyBean.class);
    private static final String PATTERN_PREFIX = "\\{[\\w\\s\\.\\=\\<\\>\\(\\)]*?:(?!";
    private static final String PATTERN_SUFFIX = ").*?\\}";

    private static final String CHECK_PATTERN = "(\\{)|(\\})";

    private static final String RESULTMAPPING_PATTERN = "\\[(.+)\\]";

    private static final String voParameterName = "voClass";

    private static final String DOT = ".";

    private static final String AND = " AND ";

    private static final String LIKE = " LIKE ";

    private ApplicationContext applicationContext;

    private Object proxyObject = null;

    private Class serviceInterface = null;

    private SessionFactory sessionFactory = null;

    public SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public Object getObject() throws Exception {
        return proxyObject;
    }

    public Class getServiceInterface() {
        return serviceInterface;
    }

    public void setServiceInterface(Class serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    public Class<?> getObjectType() {
        return getServiceInterface();
    }

    public boolean isSingleton() {
        return false;
    }

    public void afterPropertiesSet() throws Exception {
        proxyObject = ProxyFactory.getProxy(getServiceInterface(), this);
    }

    protected Session getSession() {
        return getSessionFactory().getCurrentSession();
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        String methodName = method.getName();
        Object[] objects = invocation.getArguments();
        DaoSql sqlDAO = method.getAnnotation(DaoSql.class);
        if (methodName.equals("load")) {
            return load((Class) objects[0], (Serializable) objects[1]);
        }
        if (methodName.equals("get")) {
            return get((Class) objects[0], (Serializable) objects[1]);
        }
        if (methodName.equals("update")) {
            return update(objects[0]);
        }
        if (methodName.equals("deleteById")) {
            deleteById((Class) objects[0], (Serializable) objects[1]);
            return invocation.getMethod().getGenericReturnType();
        }
        if (methodName.equals("save")) {
            return save(objects[0]);
        }
        if (methodName.equals("countRecord")) {
            return countRecord((String) objects[0]);
        }
        if (methodName.equals("getSession")) {
            return getSession();
        }
        if (sqlDAO.isInsert() == true || sqlDAO.isUpdate() == true) {
            SearchParameter searchParameter = null;
            if (objects.length > 0) {
                searchParameter = (SearchParameter) objects[0];
            }

            String sqlStatements = sqlDAO.sql();
            executeQuery(sqlStatements, searchParameter);
            return invocation.getMethod().getGenericReturnType();
        }
        if (sqlDAO != null) {
            SearchParameter searchParameter = null;
            if (objects.length > 0) {
                searchParameter = (SearchParameter) objects[0];
            }

            String sqlStatements = sqlDAO.sql();
            String resultType = sqlDAO.resultType();
            Pattern pattern = Pattern.compile(this.RESULTMAPPING_PATTERN);
            Matcher matcher = pattern.matcher(resultType);
            if (matcher.find()) {
                String type = resultType.substring(0, matcher.start());
                String mapping = matcher.group(1);
                if (type.equals("List") || type.equals("listVo")) {
                    return searchQueryNameList(sqlStatements, searchParameter, mapping);
                } else if (type.equals("Page") || type.equals("pageVo")) {
                    return searchQueryNamePageList(sqlStatements, searchParameter, mapping);
                } else {
                    return searchQueryNameUnique(sqlStatements, searchParameter, mapping);
                }
            } else {
                throw new RuntimeException("No resultmapping name");
            }


        }
        return null;
    }

    protected Serializable save(Object obj) {
        return getSession().save(obj);
    }

    protected void delete(Object obj) {
        getSession().delete(obj);
    }

    protected void deleteById(Class clazz, Serializable id) {
        delete(load(clazz, id));
    }

    protected Object update(Object obj) {
        return getSession().merge(obj);
    }

    protected <T> T load(Class clazz, Serializable id) {
        T obj = (T) getSession().load(clazz, id);
        Hibernate.initialize(obj);
        return obj;
    }

    protected <T> T get(Class clazz, Serializable id) {
        T obj = (T) getSession().get(clazz, id);
        Hibernate.initialize(obj);
        return obj;
    }

    public void executeQuery(String sqlStatements, SearchParameter params) {
        Query q = this.sessionFactory.getCurrentSession().createSQLQuery(sqlStatements);
        String[] queryParam = q.getNamedParameters();
        for (String p : queryParam) {
            Object obj = params.getParam(p);
            if (obj instanceof Collection<?>) {
                q.setParameterList(p, (Collection<?>) obj);
            } else if (obj instanceof Object[]) {
                q.setParameterList(p, (Object[]) obj);
            } else {
                q.setParameter(p, obj);
            }
        }
        q.executeUpdate();
    }

    public <T> T searchQueryNameUnique(String sqlStatements, SearchParameter params, String mapping) {
        params.setPagination(false);

        SearchResult<T> result = searchQueryNameList(sqlStatements, params, mapping);
        if (result.isUniqueObject()) {
            return result.getFirstObject();
        } else {
            return null;
        }
    }

    public <T> SearchResult<T> searchQueryNameList(String sqlStatements, SearchParameter params, String mapping) {
        SearchResult<T> result = new SearchResult<T>();
        Query q = rebuiltQuery(sqlStatements, params, mapping);

        String[] queryParam = q.getNamedParameters();

        for (String p : queryParam) {
            Object obj = params.getParam(p);
            if (obj instanceof Collection<?>) {
                q.setParameterList(p, (Collection<?>) obj);
            } else if (obj instanceof Object[]) {
                q.setParameterList(p, (Object[]) obj);
            } else {
                q.setParameter(p, obj);
            }
        }

        if (params != null && params.isPagination()) {
            result.setPageSize(params.getPageSize());
            Query totalQuery = this.sessionFactory.getCurrentSession().createSQLQuery("SELECT COUNT(1) as totalReocrd FROM ( " + q.getQueryString() + " ) reocrdCountTBL ");
            for (String p : queryParam) {
                Object obj = params.getParam(p);
                if (obj instanceof Collection<?>) {
                    totalQuery.setParameterList(p, (Collection<?>) obj);
                } else if (obj instanceof Object[]) {
                    totalQuery.setParameterList(p, (Object[]) obj);
                } else {
                    totalQuery.setParameter(p, obj);
                }
            }

            int totalRecord = ((BigInteger) totalQuery.uniqueResult()).intValue();
            result.setTotal(totalRecord);
            if (totalRecord % params.getPageSize() == 0) {
                result.setTotalPage(totalRecord / params.getPageSize());
            } else {
                result.setTotalPage((totalRecord + (params.getPageSize() - totalRecord % params.getPageSize())) / params.getPageSize());
            }

            result.setPageIdx(params.getPageIdx());

            int startPos = params.getPageSize() * (params.getPageIdx() - 1);

            q.setFirstResult(startPos);
            q.setMaxResults(params.getPageSize());


        }

        result.setResult(q.list());
        return result;
    }

    public <T> SearchPageResult<T> searchQueryNamePageList(String sqlStatements, SearchParameter params, String resultType) {
        long start = System.currentTimeMillis();

        SearchPageResult<T> result = new SearchPageResult<T>();
        Query q = rebuiltQuery(sqlStatements, params, resultType);

        String[] queryParam = q.getNamedParameters();

        for (String p : queryParam) {
            //	q.setParameter(p,params.getParam(p));

            Object obj = params.getParam(p);
            if (obj instanceof Collection<?>) {
                q.setParameterList(p, (Collection<?>) obj);
            } else if (obj instanceof Object[]) {
                q.setParameterList(p, (Object[]) obj);
            } else {
                q.setParameter(p, obj);
            }
        }

        //System.out.println("query string is :"+q.getQueryString());


        if (params.isPagination()) {
            result.setPageSize(params.getPageSize());
            Query totalQuery = this.sessionFactory.getCurrentSession().createSQLQuery("SELECT COUNT(1) as totalReocrd FROM ( " + q.getQueryString() + " ) reocrdCountTBL ");
            for (String p : queryParam) {
                Object obj = params.getParam(p);
                if (obj instanceof Collection<?>) {
                    totalQuery.setParameterList(p, (Collection<?>) obj);
                } else if (obj instanceof Object[]) {
                    totalQuery.setParameterList(p, (Object[]) obj);
                } else {
                    totalQuery.setParameter(p, obj);
                }
            }

            int totalRecord = ((BigInteger) totalQuery.uniqueResult()).intValue();
            result.setTotal(totalRecord);
            if (totalRecord % params.getPageSize() == 0) {
                result.setTotalPage(totalRecord / params.getPageSize());
            } else {
                result.setTotalPage((totalRecord + (params.getPageSize() - totalRecord % params.getPageSize())) / params.getPageSize());
            }

            result.setPageIdx(params.getPageIdx());

            int startPos = params.getPageSize() * (params.getPageIdx() - 1);

            q.setFirstResult(startPos);
            q.setMaxResults(params.getPageSize());


        }
        long middle = System.currentTimeMillis();

//		 System.out.println("stufff job kill  " +(middle-start)+" mils");
        result.setResult(q.list());
        long end = System.currentTimeMillis();
        //	 System.out.println("query job kill "+(end -middle)+" mils ");
        return result;
    }

    protected <T> T loadByUniqueId(Class clazz, String col, Object v) {
        T obj = (T) getSession().byNaturalId(clazz).using(col, v).load();
        Hibernate.initialize(obj);
        return obj;
    }

    protected int countRecord(String sql) {
        return ((BigInteger) getSession().createSQLQuery(sql).uniqueResult()).intValue();
    }

    private Query rebuiltQuery(String sqlStatements, SearchParameter params, String mapping) {
        Query q = this.sessionFactory.getCurrentSession().createSQLQuery(sqlStatements);


        String sql = q.getQueryString();

        Pattern p2 = Pattern.compile(CHECK_PATTERN);
        Class voClass = null;
        if (params != null && params.getParam(voParameterName) != null) {
            voClass = (Class) params.getParam(voParameterName);
        }
        if (p2.matcher(sql).find()) {
            String[] queryParam = q.getNamedParameters();
            String includeParam = "NA";
            for (String p : queryParam) {
                if (params != null && params.getParam(p) != null) {
                    includeParam += "|" + p;
                }
            }

            Pattern p = Pattern.compile(PATTERN_PREFIX + includeParam + PATTERN_SUFFIX);
            sql = p.matcher(sql).replaceAll("");


            sql = p2.matcher(sql).replaceAll("");

            SQLQuery result = this.sessionFactory.getCurrentSession().createSQLQuery(sql);
            if (voClass != null) {
                result.setResultTransformer(new VoResultTransformer(voClass));
            } else if ("DynamicMap".equals(mapping)) {
                result.setResultTransformer(new DynamicResultTransformer());
            } else {
                result.setResultSetMapping(mapping);
            }
            return result;
        } else {
            SQLQuery result = this.sessionFactory.getCurrentSession().createSQLQuery(sql);
            if (voClass != null) {
                result.setResultTransformer(new VoResultTransformer(voClass));
            } else if ("DynamicMap".equals(mapping)) {
                result.setResultTransformer(new DynamicResultTransformer());
            } else {
                result.setResultSetMapping(mapping);
            }
            return result;
        }

    }
}
